using System;
using System.Diagnostics;
using System.Globalization;
using System.Net.Sockets;
using System.Text;

namespace Circa.Protocol.Irc {
    public class Connection: IDisposable {
        protected struct ConnectionIoState {
            public byte[ ] Buffer;
            public Connection Connection;
        }

        public string HostName { get; set; }
        public int Port { get; set; }

        protected TcpClient sock;
        protected NetworkStream ns;
        protected string inputbuffer;
        protected bool disposed;

        ~Connection( ) {
            Dispose( false );
        }

        public void Dispose( ) {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        protected virtual void Dispose( bool disposing ) {
            if ( !disposed ) {
                if ( disposing ) {
                    /* XXX dispose of stuff */
                    sock.Close( );
                    ns.Dispose( );
                }
                /* XXX release stuff */
                inputbuffer = null;
            }
            disposed = true;
        }

        public void Close( ) {
            Dispose( );
        }

        public event EventHandler<EventArgs> Connected;
        protected virtual void OnConnected( EventArgs e ) {
            EventHandler<EventArgs> handler = Connected;
            if ( null != handler )
                handler( this, e );
        }

        public event EventHandler<NetworkErrorEventArgs> ConnectError;
        protected virtual void OnConnectError( NetworkErrorEventArgs e ) {
            EventHandler<NetworkErrorEventArgs> handler = ConnectError;
            if ( null != handler )
                handler( this, e );
        }

        public event EventHandler<NetworkErrorEventArgs> ReadError;
        protected virtual void OnReadError( NetworkErrorEventArgs e ) {
            EventHandler<NetworkErrorEventArgs> handler = ReadError;
            if ( null != handler )
                handler( this, e );
        }

        public event EventHandler<NetworkErrorEventArgs> WriteError;
        protected virtual void OnWriteError( NetworkErrorEventArgs e ) {
            EventHandler<NetworkErrorEventArgs> handler = WriteError;
            if ( null != handler )
                handler( this, e );
        }

        public event EventHandler<LineEventArgs> Line;
        protected virtual void OnLine( LineEventArgs e ) {
            EventHandler<LineEventArgs> handler = Line;
            if ( null != handler )
                handler( this, e );
        }

        public Connection( string _hostName, int _port ) {
            HostName = _hostName;
            Port = _port;
        }

        public virtual bool IsConnected( ) {
            if ( null == sock )
                return false;
            return sock.Connected;
        }

        public virtual void Connect( ) {
            sock = new TcpClient( );
            sock.BeginConnect( HostName, Port, new AsyncCallback( ConnectCallback ), this );
        }

        public void Disconnect( ) {
            Send( "QUIT :{0}", "Leaving" );
        }

        private static void ConnectCallback( IAsyncResult ar ) {
            ( (Connection) ar.AsyncState ).ConnectDone( ar );
        }

        private void ConnectDone( IAsyncResult ar ) {
            try {
                sock.EndConnect( ar );
            }
            catch ( Exception ex ) {
                OnConnectError( new NetworkErrorEventArgs( ex ) );
                return;
            }
            ns = sock.GetStream( );
            OnConnected( EventArgs.Empty );
            StartRead( );
        }

        protected void StartRead( ) {
            ConnectionIoState io = new ConnectionIoState { Buffer = new byte[ 4096 ], Connection = this };
            try {
                ns.BeginRead( io.Buffer, 0, io.Buffer.Length, new AsyncCallback( ReadCallback ), io );
            }
            catch ( Exception e ) {
                OnReadError( new NetworkErrorEventArgs( e ) );
            }
        }

        private static void ReadCallback( IAsyncResult ar ) {
            ConnectionIoState io = ( (ConnectionIoState) ar.AsyncState );
            io.Connection.ReadDone( ar, io );
        }

        private void ReadDone( IAsyncResult ar, ConnectionIoState io ) {
            int bytesRead = 0;
            try {
                bytesRead = ns.EndRead( ar );
            }
            catch ( Exception ex ) {
                OnReadError( new NetworkErrorEventArgs( ex ) );
            }
            if ( 0 == bytesRead ) {
                return;
            }

            inputbuffer += Encoding.UTF8.GetString( io.Buffer, 0, bytesRead );
            int len;
            while ( ( len = inputbuffer.IndexOf( "\r\n", StringComparison.Ordinal ) ) > -1 ) {
                string line = inputbuffer.Substring( 0, len );
                inputbuffer = inputbuffer.Remove( 0, len + 2 );
                IrcMessage m = new IrcMessage( line );
                OnLine( new LineEventArgs( m ) );
            }

            StartRead( );
        }


        public void Send( string format, params object[] args ) {
            this.Send( string.Format( CultureInfo.InvariantCulture, format, args ) );
        }

        public void Send( string str ) {
            int n = str.Length;
            char[] chars = new char[ n + 2 ];

            str.CopyTo( 0, chars, 0, n );
            while ( Encoding.UTF8.GetByteCount( chars, 0, n ) > 510 ) {
                n--;
            }
            "\r\n".CopyTo( 0, chars, n, 2 );
            n += 2;

            Debug.Print( "Connection.Send: sending {{{0}}}", str.Substring( 0, n - 2 ) );
            
            ConnectionIoState io = new ConnectionIoState {
                Buffer = Encoding.UTF8.GetBytes( chars, 0, n ),
                Connection = this
            };
            IAsyncResult ar;

            try {
                ar = ns.BeginWrite( io.Buffer, 0, io.Buffer.Length, new AsyncCallback( WriteCallback ), io );
                if ( ar.IsCompleted ) {
                    // XXX
                    Debug.Print( "Connection.Send: complete already?? CompletedSynchronously={0}", ar.CompletedSynchronously ? "TRUE" : "false" );
                }
            }
            catch ( Exception ex ) {
                OnWriteError( new NetworkErrorEventArgs( ex ) );
                return;
            }
        }

        private static void WriteCallback( IAsyncResult ar ) {
            ConnectionIoState io = ( (ConnectionIoState) ar.AsyncState );
            io.Connection.WriteDone( ar, io );
        }

        private void WriteDone( IAsyncResult ar, ConnectionIoState io ) {
            try {
                ns.EndWrite( ar );
            }
            catch ( Exception ex ) {
                OnWriteError( new NetworkErrorEventArgs( ex ) );
            }
        }

    }

}
